<!DOCTYPE html>
<html>
  <head>
    <title>
      dom-exceptions.html
    </title>
    <script src="../resources/testharness.js"></script>
    <script src="../resources/testharnessreport.js"></script>
    <script src="resources/audit-util.js"></script>
    <script src="resources/audit.js"></script>
    <script src="../resources/sab-polyfill.js"></script>
  </head>
  <body>
    <script id="layout-test-code">
      let audit = Audit.createTaskRunner();

      let context;
      let otherContext;
      let node;
      let node2;
      let mode;
      let panner;
      let script;
      let source;

      function shouldThrowAndBeUnchanged(should, node, attr, value) {
        should(
            () => node[attr] = value,
            node.constructor.name + '.' + attr + ' = ' + value)
            .throw();
        should(node[attr], node.constructor.name + '.' + attr)
            .notBeEqualTo(value);
      }

      audit.define(
          {label: 'initialize', description: 'Initialize contexts for testing'},
          (task, should) => {

            should(() => {
              context = new AudioContext();
            }, 'context = new AudioContext()').notThrow();

            should(() => {
              otherContext = new AudioContext();
            }, 'otherContext = new AudioContext()').notThrow();

            task.done();
          });

      audit.define('createBuffer', (task, should) => {
        // Invalid number of channels: NotSupportedError
        should(
            () => context.createBuffer(99, 1, context.sampleRate),
            'context.createBuffer(99, 1, context.sampleRate)')
            .throw(DOMException, 'NotSupportedError');
        should(
            () => context.createBuffer(0, 1, context.sampleRate),
            'context.createBuffer(0, 1, context.sampleRate)')
            .throw(DOMException, 'NotSupportedError');
        // Invalid sample rate: NotSupportedError
        should(
            () => context.createBuffer(1, 1, 1),
            'context.createBuffer(1, 1, 1)')
            .throw(DOMException, 'NotSupportedError');
        should(
            () => context.createBuffer(1, 1, 2999),
            'context.createBuffer(1, 1, 2999)')
            .throw(DOMException, 'NotSupportedError');
        should(
            () => context.createBuffer(1, 1, 768001),
            'context.createBuffer(1, 1, 768001)')
            .throw(DOMException, 'NotSupportedError');
        should(
            () => context.createBuffer(1, 1, 1e6),
            'context.createBuffer(1, 1, 1e6)')
            .throw(DOMException, 'NotSupportedError');
        // Check valid values from crbug.com/344375
        should(
            () => context.createBuffer(1, 1, 3000),
            'context.createBuffer(1, 1, 3000)')
            .notThrow();
        should(
            () => context.createBuffer(1, 1, 192000),
            'context.createBuffer(1, 1, 192000)')
            .notThrow();
        should(
            () => context.createBuffer(1, 1, 768000),
            'context.createBuffer(1, 1, 768000)')
            .notThrow();
        // Invalid number of frames: NotSupportedError
        should(
            () => context.createBuffer(1, 0, context.sampleRate),
            'context.createBuffer(1, 0, context.sampleRate)')
            .throw(DOMException, 'NotSupportedError');
        // 2-arg createBuffer not allowed.
        should(
            () => context.createBuffer(new ArrayBuffer(100), true),
            'context.createBuffer(new ArrayBuffer(100), true)')
            .throw(TypeError);

        task.done();
      });

      audit.define('createMediaElementSource', (task, should) => {
        // Invalid sources (unspecified error)
        should(
            () => context.createMediaElementSource(null),
            'context.createMediaElementSource(null)')
            .throw();
        task.done();
      });

      audit.define('createMediaStreamSource', (task, should) => {
        // Invalid sources (unspecified error)
        should(
            () => context.createMediaStreamSource(null),
            'context.createMediaStreamSource(null)')
            .throw();

        task.done();
      });

      audit.define('createScriptProcessor', (task, should) => {
        // Invalid buffer size: IndexSizeError
        should(
            () => context.createScriptProcessor(1, 1, 1),
            'context.createScriptProcessor(1, 1, 1)')
            .throw(DOMException, 'IndexSizeError');
        // Invalid number of inputs and outputs: IndexSizeError
        should(
            () => context.createScriptProcessor(4096, 100, 1),
            'context.createScriptProcessor(4096, 100, 1)')
            .throw(DOMException, 'IndexSizeError');
        should(
            () => context.createScriptProcessor(4096, 1, 100),
            'context.createScriptProcessor(4096, 1, 100)')
            .throw(DOMException, 'IndexSizeError');
        should(
            () => context.createScriptProcessor(),
            'context.createScriptProcessor()')
            .notThrow();
        should(
            () => context.createScriptProcessor(0),
            'context.createScriptProcessor(0)')
            .notThrow();

        task.done();
      });

      audit.define('createChannelSplitter', (task, should) => {
        // Invalid number of channels: IndexSizeError
        should(
            () => context.createChannelSplitter(0),
            'context.createChannelSplitter(0)')
            .throw(DOMException, 'IndexSizeError');
        should(
            () => context.createChannelSplitter(99),
            'context.createChannelSplitter(99)')
            .throw(DOMException, 'IndexSizeError');
        should(
            () => context.createChannelMerger(0),
            'context.createChannelMerger(0)')
            .throw(DOMException, 'IndexSizeError');

        task.done();
      });

      audit.define('createChannelMerger', (task, should) => {
        // Invalid number of channels: IndexSizeError
        should(
            () => context.createChannelMerger(99),
            'context.createChannelMerger(99)')
            .throw(DOMException, 'IndexSizeError');

        task.done();
      });

      audit.define('createPeriodicWave', (task, should) => {
        // Invalid real/imag arrays: IndexSizeError
        should(
            () => context.createPeriodicWave(null, null),
            'context.createPeriodicWave(null, null)')
            .throw(TypeError);
        should(
            () => context.createPeriodicWave(new Float32Array(10), null),
            'context.createPeriodicWave(new Float32Array(10), null)')
            .throw(TypeError);
        // Verify that we can use large arrays with no limit. Roughly.
        should(
            () => context.createPeriodicWave(
                new Float32Array(4100), new Float32Array(4100)),
            'context.createPeriodicWave(new Float32Array(4100), new Float32Array(4100))')
            .notThrow();
        should(
            () => context.createPeriodicWave(
                new Float32Array(8192), new Float32Array(8192)),
            'context.createPeriodicWave(new Float32Array(8192), new Float32Array(8192))')
            .notThrow();
        should(
            () => context.createPeriodicWave(
                new Float32Array(10000), new Float32Array(10000)),
            'context.createPeriodicWave(new Float32Array(10000), new Float32Array(10000))')
            .notThrow();
        // Real and imaginary arrays must have the same size: IndexSizeError
        should(
            () => context.createPeriodicWave(
                new Float32Array(10), new Float32Array(7)),
            'context.createPeriodicWave(new Float32Array(10), new Float32Array(7))')
            .throw(DOMException, 'IndexSizeError');

        task.done();
      });

      audit.define('createAnalyser', (task, should) => {
        // Analysers
        node = context.createAnalyser();
        // Invalid fftSize: IndexSizeError
        shouldThrowAndBeUnchanged(should, node, 'fftSize', '42');
        shouldThrowAndBeUnchanged(should, node, 'fftSize', '16');
        should(() => node.fftSize = 32768, 'AnalyserNode.fftSize = 32768')
            .notThrow();
        shouldThrowAndBeUnchanged(should, node, 'fftSize', '65536');

        shouldThrowAndBeUnchanged(should, node, 'minDecibels', '-10');
        shouldThrowAndBeUnchanged(should, node, 'maxDecibels', '-150');
        shouldThrowAndBeUnchanged(should, node, 'minDecibels', '-30');
        shouldThrowAndBeUnchanged(should, node, 'maxDecibels', '-100');

        shouldThrowAndBeUnchanged(
            should, node, 'smoothingTimeConstant', '-0.1');
        shouldThrowAndBeUnchanged(should, node, 'smoothingTimeConstant', '1.5');

        should(
            () => node.getFloatFrequencyData(null),
            'AnalyserNode.getFloatFrequencyData(null)')
            .throw();
        should(
            () => node.getByteFrequencyData(null),
            node.constructor.name + '.getByteFrequencyData(null)')
            .throw();
        should(
            () => node.getFloatTimeDomainData(null),
            node.constructor.name + '.getFloatTimeDomainData(null)')
            .throw();
        should(
            () => node.getByteTimeDomainData(null),
            node.constructor.name + '.getByteTimeDomainData(null)')
            .throw();

        if (window.SharedArrayBuffer) {
          should(
              () => node.getFloatFrequencyData(
                  new Float32Array(new SharedArrayBuffer(16))),
              'AnalyserNode.getFloatFrequencyData(SharedArrayBuffer view)')
              .throw();
          should(
              () => node.getByteFrequencyData(
                  new Uint8Array(new SharedArrayBuffer(16))),
              node.constructor.name +
                  '.getByteFrequencyData(SharedArrayBuffer view)')
              .throw();
          should(
              () => node.getFloatTimeDomainData(
                  new Float32Array(new SharedArrayBuffer(16))),
              node.constructor.name +
                  '.getFloatTimeDomainData(SharedArrayBuffer view)')
              .throw();
          should(
              () => node.getByteTimeDomainData(
                  new Uint8Array(new SharedArrayBuffer(16))),
              node.constructor.name +
                  '.getByteTimeDomainData(SharedArrayBuffer view)')
              .throw();
        }

        // AudioBuffers
        node = context.createBuffer(1, 1, context.sampleRate);
        // Invalid channel index: IndexSizeError
        should(
            () => node.getChannelData(2),
            node.constructor.name + '.getChannelData(2)')
            .throw();

        task.done();
      });

      audit.define(
          {
            label: 'Init test nodes',
            description: 'Create test nodes for the following tests'
          },
          (task, should) => {
            should(() => {
              node = context.createGain();
            }, 'node = context.createGain()').notThrow();
            should(() => {
              node2 = context.createGain();
            }, 'node2 = context.createGain()').notThrow();

            task.done();
          });

      audit.define(
          {label: 'connections', description: 'AudioNode connections'},
          (task, should) => {

            // AudioNode connections
            // Invalid destination node (unspecified error)
            should(() => node.connect(null, 0, 0), 'node.connect(null, 0, 0)')
                .throw();
            // Invalid input or output index: IndexSizeError
            should(
                () => node.connect(context.destination, 100, 0),
                'node.connect(context.destination, 100, 0)')
                .throw(DOMException, 'IndexSizeError');
            should(
                () => node.connect(context.destination, 0, 100),
                'node.connect(context.destination, 0, 100)')
                .throw(DOMException, 'IndexSizeError');
            should(
                () => node.connect(node2.gain, 100),
                'node.connect(node2.gain, 100)')
                .throw(DOMException, 'IndexSizeError');
            should(() => node.disconnect(99), 'node.disconnect(99)')
                .throw(DOMException, 'IndexSizeError');
            // Can't connect to a different context (unspecified error)
            should(
                () => node.connect(otherContext.destination),
                'node.connect(otherContext.destination)')
                .throw();

            task.done();
          });

      audit.define(
          {
            label: 'channel-stuff',
            description: 'channelCount, channelCountMode, channelInterpretation'
          },
          (task, should) => {

            // Invalid channel count: NotSupportedError
            shouldThrowAndBeUnchanged(should, node, 'channelCount', '99');
            // Invalid mode or interpretation (unspecified error)
            currentMode = node.channelCountMode;
            currentInterpretation = node.channelInterpretation;
            should(
                () => node.channelCountMode = 'fancy',
                'node.channelCountMode = "fancy"')
                .notThrow();
            should(node.channelCountMode, 'node.channelCountMode')
                .beEqualTo(currentMode);
            should(
                () => node.channelInterpretation = mode,
                'node.channelInterpretation = mode')
                .notThrow();
            should(node.channelInterpretation, 'node.channelInterpretation')
                .beEqualTo(currentInterpretation);
            // Destination node channel count: should throw IndexSizeError on
            // invalid channel count. shouldNotThrow() method cannot be used
            // because the error message includes the number of channels, which
            // can change depending on the actual attached hardware.
            should(
                () => context.destination.channelCount = 99,
                'context.destination.channelCount = 99')
                .throw(DOMException, 'IndexSizeError', {omitErrorMessage: true});

            task.done();
          });

      audit.define('audioparam', (task, should) => {
        // AudioParams
        param = context.createGain().gain;
        should(
            () => param.setValueCurveAtTime(null, 0, 0),
            'param.setValueCurveAtTime(null, 0, 0)')
            .throw();

        // exponentialRampToValue should throw only for "zero" target values.
        should(
            () => node.gain.exponentialRampToValueAtTime(-1, 0.1),
            'node.gain.exponentialRampToValueAtTime(-1, 0.1)')
            .notThrow();
        should(
            () => node.gain.exponentialRampToValueAtTime(0, 0.1),
            'node.gain.exponentialRampToValueAtTime(0, 0.1)')
            .throw();
        // 1e-100 is 0 when converted to a single precision float.
        should(
            () => node.gain.exponentialRampToValueAtTime(1e-100, 0.1),
            'node.gain.exponentialRampToValueAtTime(1e-100, 0.1)')
            .throw();
        // See crbug.com/459391.
        // Math.pow(2, -149) = 1.401298464324817e-45 is the double-float value
        // of the least positive single float number. We do it this way to make
        // sure no round-off or conversion errors happen when reading
        // 1.401298464324817e-45.
        should(
            () =>
                node.gain.exponentialRampToValueAtTime(Math.pow(2, -149), 0.1),
            'node.gain.exponentialRampToValueAtTime(Math.pow(2, -149), 0.1)')
            .notThrow();
        // Math.pow(2, -150) = 7.006492321624085d-46 is the largest double float
        // value such that conversion to a float produces 0.  Any larger value
        // would produce a non-zero value when converted to a single float.
        should(
            () =>
                node.gain.exponentialRampToValueAtTime(Math.pow(2, -150), 0.1),
            'node.gain.exponentialRampToValueAtTime(Math.pow(2, -150), 0.1)')
            .throw();

        task.done();
      });

      audit.define('biquad', (task, should) => {
        // BiquadFilterNode
        node = context.createBiquadFilter();
        should(
            () => node.getFrequencyResponse(
                new Float32Array(1), new Float32Array(1), new Float32Array(1)),
            'node.getFrequencyResponse(new Float32Array(1), new Float32Array(1), new Float32Array(1))')
            .notThrow();
        should(
            () => node.getFrequencyResponse(
                null, new Float32Array(1), new Float32Array(1)),
            'node.getFrequencyResponse(null, new Float32Array(1), new Float32Array(1))')
            .throw();
        should(
            () => node.getFrequencyResponse(
                new Float32Array(1), null, new Float32Array(1)),
            'node.getFrequencyResponse(new Float32Array(1), null, new Float32Array(1))')
            .throw();
        should(
            () => node.getFrequencyResponse(
                new Float32Array(1), new Float32Array(1), null),
            'node.getFrequencyResponse(new Float32Array(1), new Float32Array(1), null)')
            .throw();

        if (window.SharedArrayBuffer) {
          let shared_view = new Float32Array(new SharedArrayBuffer(4));
          let nonshared_view = new Float32Array(1);

          should(
              () => node.getFrequencyResponse(
                  shared_view, nonshared_view, nonshared_view),
              'node.getFrequencyResponse(shared_view, nonshared_view, nonshared_view)')
              .throw();
          should(
              () => node.getFrequencyResponse(
                  nonshared_view, shared_view, nonshared_view),
              'node.getFrequencyResponse(nonshared_view, shared_view, nonshared_view)')
              .throw();
          should(
              () => node.getFrequencyResponse(
                  nonshared_view, nonshared_view, shared_view),
              'node.getFrequencyResponse(nonshared_view, nonshared_view, shared_view)')
              .throw();
        }

        task.done();
      });

      audit.define(
          {label: 'offline-audio-context', description: 'supports 32 channels'},
          (task, should) => {
            // OfflineAudioContext
            // Max supported channels
            should(
                () => new OfflineAudioContext(32, 100, context.sampleRate),
                'new OfflineAudioContext(32, 100, context.sampleRate)')
                .notThrow();
            task.done();
          });

      audit.define(
          {
            label: 'invalid-offline-audio-context-parameters',
            description: 'errors for invalid channel counts'
          },
          (task, should) => {
            // Invalid number of channels
            should(
                () => new OfflineAudioContext(0, 100, context.sampleRate),
                'new OfflineAudioContext(0, 100, context.sampleRate)')
                .throw(DOMException, 'NotSupportedError');
            should(
                () => new OfflineAudioContext(99, 100, context.sampleRate),
                'new OfflineAudioContext(99, 100, context.sampleRate)')
                .throw(DOMException, 'NotSupportedError');
            // Invalid sample rate.
            should(
                () => new OfflineAudioContext(1, 100, 1),
                'new OfflineAudioContext(1, 100, 1)')
                .throw(DOMException, 'NotSupportedError');
            should(
                () => new OfflineAudioContext(1, 100, 1e6),
                'new OfflineAudioContext(1, 100, 1e6)')
                .throw(DOMException, 'NotSupportedError');
            should(
                () => new OfflineAudioContext(1, 0, 44100),
                'new OfflineAudioContext(1, 0, 44100)')
                .throw(DOMException, 'NotSupportedError');
            task.done();
          });

      audit.define('invalid-frame-length', (task, should) => {
        // Invalid frame length (crbug.com/351277)
        let testContext;

        should(
            () => testContext =
                new OfflineAudioContext(1, -88200000000000, 44100),
            'testContext = new OfflineAudioContext(1, -88200000000000, 44100)')
            .notThrow();
        should(testContext.startRendering(), 'testContext.startRendering()')
            .beRejected()
            .then(() => task.done());
      });

      audit.define('waveshaper', (task, should) => {
        // WaveShaper types
        node = context.createWaveShaper();
        currentOversample = node.oversample;
        should(() => node.oversample = '9x', 'node.oversample = "9x"')
            .notThrow();
        should(node.oversample, 'node.oversample').beEqualTo(currentOversample);
        should(() => node.curve = {}, 'node.curve = {}').throw();
        should(
            () => node.curve = new Float32Array(1),
            'node.curve = new Float32Array(1)')
            .throw();
        should(node.curve, 'node.curve').beEqualTo(null);
        should(
            () => node.curve = new Float32Array(2),
            'node.curve = new Float32Array(2)')
            .notThrow();
        should(() => node.curve = null, 'node.curve = null').notThrow();

        task.done();
      });

      audit.define(
          {
            label: 'audio-buffer-source',
            description: 'AudioBufferSource start/stop'
          },
          (task, should) => {
            // Start/stop for AudioBufferSourceNodes
            buffer = context.createBuffer(1, 1, context.sampleRate);
            should(
                () => source = context.createBufferSource(),
                'source = context.createBufferSource()')
                .notThrow();
            should(() => {source.buffer = buffer}, 'source.buffer = buffer')
                .notThrow();
            should(
                () => source.buffer =
                    context.createBuffer(1, 10, context.sampleRate),
                'source.buffer = context.createBuffer(1, 10, context.sampleRate)')
                .throw();
            should(() => {source.start(-1)}, 'source.start(-1)').throw();
            should(() => {source.start(Infinity)}, 'source.start(Infinity)')
                .throw();
            should(() => {source.start(-Infinity)}, 'source.start(-Infinity)')
                .throw();
            should(() => {source.start(NaN)}, 'source.start(NaN)').throw();
            should(
                () => {source.start(1, Infinity)}, 'source.start(1, Infinity)')
                .throw();
            should(
                () => {source.start(1, -Infinity)},
                'source.start(1, -Infinity)')
                .throw();
            should(() => {source.start(1, NaN)}, 'source.start(1, NaN)')
                .throw();
            should(() => {source.start(1, -1)}, 'source.start(1, -1)').throw();
            should(
                () => {source.start(1, -Number.MIN_VALUE)},
                'source.start(1, -Number.MIN_VALUE)')
                .throw();
            should(
                () => {source.start(1, 1, Infinity)},
                'source.start(1, 1, Infinity)')
                .throw();
            should(
                () => {source.start(1, 1, -Infinity)},
                'source.start(1, 1, -Infinity)')
                .throw();
            should(() => {source.start(1, 1, NaN)}, 'source.start(1, 1, NaN)')
                .throw();
            should(() => {source.start(1, 1, -1)}, 'source.start(1, 1, -1)')
                .throw();
            should(
                () => {source.start(1, 1, -Number.MIN_VALUE)},
                'source.start(1, 1, -Number.MIN_VALUE)')
                .throw();
            should(() => source.start(), 'source.start()').notThrow();
            should(
                () => source.stop(-Number.MIN_VALUE),
                'source.stop(-Number.MIN_VALUE)')
                .throw();
            should(() => source.stop(Infinity), 'source.stop(Infinity)')
                .throw();
            should(() => source.stop(-Infinity), 'source.stop(-Infinity)')
                .throw();
            should(() => source.stop(NaN), 'source.stop(NaN)').throw();
            should(() => source.stop(), 'source.stop()').notThrow();

            // Verify that start(0, 0) doesn't signal.
            let source2;
            should(
                () => {source2 = context.createBufferSource()},
                'source2 = context.createBufferSource()')
                .notThrow();
            should(() => source2.buffer = buffer, 'source2.buffer = buffer')
                .notThrow();
            should(() => source2.start(0, 0), 'source2.start(0, 0)').notThrow();

            // Verify that start(0, -0.0) doesn't signal.
            let source3;
            should(
                () => source3 = context.createBufferSource(),
                'source3 = context.createBufferSource()')
                .notThrow();
            should(() => source3.buffer = buffer, 'source3.buffer = buffer')
                .notThrow();
            should(
                () => source3.start(0, -1 / Infinity),
                'source3.start(0, -1/Infinity)')
                .notThrow();

            // It's not clear from the spec, but I think it's valid to call
            // start(). The spec is silent on what happens if we call stop()
            // afterwards, so don't call it.
            let source4;
            should(
                () => source4 = context.createBufferSource(),
                'source4 = context.createBufferSource()')
                .notThrow();
            should(() => source4.start(), 'source4.start()').notThrow();

            buffer = context.createBuffer(1, 1, context.sampleRate);
            let source5;
            should(
                () => source5 = context.createBufferSource(),
                'source5 = context.createBufferSource()')
                .notThrow();
            should(() => source5.buffer = buffer, 'source5.buffer = buffer')
                .notThrow();
            should(() => source5.stop(), 'source5.stop()').throw();

            buffer = context.createBuffer(1, 1, context.sampleRate);
            let source6;
            should(
                () => source6 = context.createBufferSource(),
                'source6 = context.createBufferSource()')
                .notThrow();
            should(() => source6.buffer = buffer, 'source6.buffer = buffer')
                .notThrow();
            should(() => source6.start(), 'source6.start()').notThrow();
            should(() => source6.start(), 'source6.start()').throw();

            buffer = context.createBuffer(1, 1, context.sampleRate);
            let source7;
            should(
                () => source7 = context.createBufferSource(),
                'source7 = context.createBufferSource()')
                .notThrow();
            should(() => source7.buffer = buffer, 'source7.buffer = buffer')
                .notThrow();
            should(() => source7.start(), 'source7.start()').notThrow();
            should(() => source7.stop(), 'source7.stop()').notThrow();

            task.done();
          });

      audit.define(
          {label: 'oscillator', description: 'start/stop'}, (task, should) => {

            let source8;
            // Start/stop for OscillatorNodes
            should(
                () => source8 = context.createOscillator(),
                'source8 = context.createOscillator()')
                .notThrow();
            should(
                () => source8.start(-Number.MIN_VALUE),
                'source8.start(-Number.MIN_VALUE)')
                .throw();
            should(() => source8.start(Infinity), 'source8.start(Infinity)')
                .throw();
            should(() => source8.start(-Infinity), 'source8.start(-Infinity)')
                .throw();
            should(() => source8.start(NaN), 'source8.start(NaN)').throw();
            should(() => source8.start(), 'source8.start()').notThrow();
            should(
                () => source8.stop(-Number.MIN_VALUE),
                'source8.stop(-Number.MIN_VALUE)')
                .throw();
            should(() => source8.stop(Infinity), 'source8.stop(Infinity)')
                .throw();
            should(() => source8.stop(-Infinity), 'source8.stop(-Infinity)')
                .throw();
            should(() => source8.stop(NaN), 'source8.stop(NaN)').throw();
            should(() => source8.stop(), 'source8.stop()').notThrow();

            should(
                () => osc = context.createOscillator(),
                'osc = context.createOscillator()')
                .notThrow();
            should(() => osc.stop(), 'osc.stop()').throw();
            should(
                () => osc1 = context.createOscillator(),
                'osc1 = context.createOscillator()')
                .notThrow();
            should(() => osc1.start(), 'osc1.start()').notThrow();
            should(() => osc1.stop(), 'osc1.stop()').notThrow();

            should(() => osc.setPeriodicWave(null), 'osc.setPeriodicWave(null)')
                .throw();


            task.done();
          });

      audit.define('convolver', (task, should) => {
        // Convolver buffer rate must match context rate. Create on offline
        // context so we specify the context rate exactly, in case the test is
        // run on platforms with different HW sample rates.
        let oc;

        should(
            () => oc = new OfflineAudioContext(1, 44100, 44100),
            'oc = new OfflineAudioContext(1, 44100, 44100)')
            .notThrow();
        should(() => conv = oc.createConvolver(), 'conv = oc.createConvolver()')
            .notThrow();
        should(() => conv.buffer = {}, 'conv.buffer = {}').throw();
        should(
            () => conv.buffer = oc.createBuffer(1, 100, 22050),
            'conv.buffer = oc.createBuffer(1, 100, 22050)')
            .throw();
        // conv.buffer should be unchanged (null) because the above failed.
        should(conv.buffer, 'conv.buffer').beEqualTo(null);

        task.done();
      });

      audit.define('panner', (task, should) => {
        // PannerNode channel count and mode
        panner = context.createPanner();
        // Channel count can only be set to 1 or 2.
        should(() => panner.channelCount = 1, 'panner.channelCount = 1')
            .notThrow();
        should(() => panner.channelCount = 2, 'panner.channelCount = 2')
            .notThrow();
        shouldThrowAndBeUnchanged(should, panner, 'channelCount', 0);
        shouldThrowAndBeUnchanged(should, panner, 'channelCount', 3);
        // It is illegal to set the mode to 'max'
        shouldThrowAndBeUnchanged(should, panner, 'channelCountMode', 'max');
        should(
            () => panner.channelCountMode = 'explicit',
            'panner.channelCountMode = "explicit"')
            .notThrow();
        should(
            () => panner.channelCountMode = 'clamped-max',
            'panner.channelCountMode = "clamped-max"')
            .notThrow();
        should(
            () => panner.channelCountMode = 'junk',
            'panner.channelCountMode = "junk"')
            .notThrow();

        task.done();
      });

      audit.define('script-processor', (task, should) => {
        // Test channel count and mode for a ScriptProcessor.
        should(
            () => script = context.createScriptProcessor(256, 3),
            'script = context.createScriptProcessor(256, 3)')
            .notThrow();
        // Make sure the channelCount and mode are set correctly.
        should(script.channelCount, 'script.channelCount').beEqualTo(3);
        should(script.channelCountMode, 'script.channelCountMode')
            .beEqualTo('explicit');
        // Cannot change the channelCount or mode to anything else
        should(() => script.channelCount = 3, 'script.channelCount = 3')
            .notThrow();
        shouldThrowAndBeUnchanged(should, script, 'channelCount', 1);

        shouldThrowAndBeUnchanged(should, script, 'channelCount', 7);
        should(
            () => script.channelCountMode = 'explicit',
            'script.channelCountMode = "explicit"')
            .notThrow();
        shouldThrowAndBeUnchanged(should, script, 'channelCountMode', 'max');
        shouldThrowAndBeUnchanged(
            should, script, 'channelCountMode', 'clamped-max');
        should(
            () => script.channelCountMode = 'junk',
            'script.channelCountMode = "junk"')
            .notThrow();

        task.done();
      });

      audit.define(
          {label: 'misc', description: 'Miscellaneous tests'},
          (task, should) => {

            // noteOn and noteOff don't exist anymore
            should(osc.noteOn, 'osc.noteOn').beEqualTo(undefined);
            should(osc.noteOff, 'osc.noteOff').beEqualTo(undefined);
            should(source.noteOn, 'source.noteOn').beEqualTo(undefined);
            should(source.noteOff, 'source.noteOff').beEqualTo(undefined);

            task.done();
          });

      audit.run();
    </script>
  </body>
</html>
